/*
Copyright 2024 MySQL Parser Project

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package sqlparser

// Clone creates a deep clone of the input SQLNode
func Clone(node SQLNode) SQLNode {
	if node == nil {
		return nil
	}

	switch n := node.(type) {
	case *Select:
		return cloneSelect(n)
	case *Insert:
		return cloneInsert(n)
	case *Update:
		return cloneUpdate(n)
	case *Delete:
		return cloneDelete(n)
	case *CreateTableStatement:
		return cloneCreateTable(n)
	case *AlterTableStatement:
		return cloneAlterTable(n)
	case *DropTableStatement:
		return cloneDropTable(n)
	case *CreateIndexStatement:
		return cloneCreateIndex(n)
	case *DropIndexStatement:
		return cloneDropIndex(n)
	case *TruncateStatement:
		return cloneTruncate(n)
	case *CreateViewStatement:
		return cloneCreateView(n)
	case *DropViewStatement:
		return cloneDropView(n)
	case *AlterViewStatement:
		return cloneAlterView(n)
	case *ColName:
		return cloneColName(n)
	case *Literal:
		return cloneLiteral(n)
	case *NullVal:
		return cloneNullVal(n)
	case *AliasedTableExpr:
		return cloneAliasedTableExpr(n)
	case *AliasedExpr:
		return cloneAliasedExpr(n)
	case *StarExpr:
		return cloneStarExpr(n)
	case *Where:
		return cloneWhere(n)
	case *Limit:
		return cloneLimit(n)
	case *Order:
		return cloneOrder(n)
	case *UpdateExpr:
		return cloneUpdateExpr(n)
	case *Subquery:
		return cloneSubquery(n)
	case *JoinTableExpr:
		return cloneJoinTableExpr(n)
	case *OnCondition:
		return cloneOnCondition(n)
	case *UsingCondition:
		return cloneUsingCondition(n)
	case *FuncExpr:
		return cloneFuncExpr(n)
	case SelectExprs:
		return cloneSelectExprs(n)
	case TableExprs:
		return cloneTableExprs(n)
	case GroupBy:
		return cloneGroupBy(n)
	case OrderBy:
		return cloneOrderBy(n)
	case UpdateExprs:
		return cloneUpdateExprs(n)
	case Columns:
		return cloneColumns(n)
	case TableNames:
		return cloneTableNames(n)
	case TableName:
		return cloneTableName(n)
	case IdentifierCI:
		return cloneIdentifierCI(n)
	case IdentifierCS:
		return cloneIdentifierCS(n)
	default:
		// For unknown types, return the original node
		return node
	}
}

// Clone functions for each node type

func cloneSelect(node *Select) *Select {
	if node == nil {
		return nil
	}
	return &Select{
		Cache:            cloneBoolPtr(node.Cache),
		Distinct:         node.Distinct,
		StraightJoin:     node.StraightJoin,
		SQLCalcFoundRows: node.SQLCalcFoundRows,
		SelectExprs:      cloneSelectExprs(node.SelectExprs),
		From:             cloneTableExprs(node.From),
		Where:            cloneWhere(node.Where),
		GroupBy:          cloneGroupBy(node.GroupBy),
		Having:           cloneWhere(node.Having),
		OrderBy:          cloneOrderBy(node.OrderBy),
		Limit:            cloneLimit(node.Limit),
		Lock:             node.Lock,
	}
}

func cloneInsert(node *Insert) *Insert {
	if node == nil {
		return nil
	}
	return &Insert{
		Action:     node.Action,
		Ignore:     node.Ignore,
		Table:      cloneTableName(node.Table),
		Partitions: clonePartitions(node.Partitions),
		Columns:    cloneColumns(node.Columns),
		Rows:       cloneInsertRows(node.Rows),
		OnDup:      cloneOnDup(node.OnDup),
	}
}

func cloneUpdate(node *Update) *Update {
	if node == nil {
		return nil
	}
	return &Update{
		Ignore:     node.Ignore,
		TableExprs: cloneTableExprs(node.TableExprs),
		Exprs:      cloneUpdateExprs(node.Exprs),
		Where:      cloneWhere(node.Where),
		OrderBy:    cloneOrderBy(node.OrderBy),
		Limit:      cloneLimit(node.Limit),
	}
}

func cloneDelete(node *Delete) *Delete {
	if node == nil {
		return nil
	}
	return &Delete{
		Ignore:     node.Ignore,
		Targets:    cloneTableNames(node.Targets),
		TableExprs: cloneTableExprs(node.TableExprs),
		Where:      cloneWhere(node.Where),
		OrderBy:    cloneOrderBy(node.OrderBy),
		Limit:      cloneLimit(node.Limit),
	}
}

func cloneCreateTable(node *CreateTableStatement) *CreateTableStatement {
	if node == nil {
		return nil
	}
	return &CreateTableStatement{
		Table:       cloneTableNamePtr(node.Table),
		Columns:     cloneColumnDefinitions(node.Columns),
		Constraints: cloneTableConstraints(node.Constraints),
		Options:     cloneTableOptions(node.Options),
		Temporary:   node.Temporary,
		IfNotExists: node.IfNotExists,
	}
}

func cloneAlterTable(node *AlterTableStatement) *AlterTableStatement {
	if node == nil {
		return nil
	}
	return &AlterTableStatement{
		Table:   cloneTableNamePtr(node.Table),
		Actions: cloneAlterActions(node.Actions),
	}
}

func cloneDropTable(node *DropTableStatement) *DropTableStatement {
	if node == nil {
		return nil
	}
	return &DropTableStatement{
		Tables:    cloneTableNamePtrs(node.Tables),
		IfExists:  node.IfExists,
		Temporary: node.Temporary,
	}
}

func cloneCreateIndex(node *CreateIndexStatement) *CreateIndexStatement {
	if node == nil {
		return nil
	}
	return &CreateIndexStatement{
		Index:       node.Index,
		Table:       cloneTableNamePtr(node.Table),
		Columns:     cloneColNamePtrs(node.Columns),
		Unique:      node.Unique,
		IfNotExists: node.IfNotExists,
	}
}

func cloneDropIndex(node *DropIndexStatement) *DropIndexStatement {
	if node == nil {
		return nil
	}
	return &DropIndexStatement{
		Index:    node.Index,
		Table:    cloneTableNamePtr(node.Table),
		IfExists: node.IfExists,
	}
}

func cloneTruncate(node *TruncateStatement) *TruncateStatement {
	if node == nil {
		return nil
	}
	return &TruncateStatement{
		Table: cloneTableNamePtr(node.Table),
	}
}

func cloneCreateView(node *CreateViewStatement) *CreateViewStatement {
	if node == nil {
		return nil
	}
	return &CreateViewStatement{
		View:        cloneTableNamePtr(node.View),
		Columns:     cloneColumns(node.Columns),
		Select:      cloneSelect(node.Select),
		Replace:     node.Replace,
		Temporary:   node.Temporary,
		IfNotExists: node.IfNotExists,
		Algorithm:   node.Algorithm,
		Definer:     node.Definer,
		SQLSecurity: node.SQLSecurity,
		CheckOption: node.CheckOption,
	}
}

func cloneDropView(node *DropViewStatement) *DropViewStatement {
	if node == nil {
		return nil
	}
	return &DropViewStatement{
		Views:    cloneTableNamePtrs(node.Views),
		IfExists: node.IfExists,
		Restrict: node.Restrict,
		Cascade:  node.Cascade,
	}
}

func cloneAlterView(node *AlterViewStatement) *AlterViewStatement {
	if node == nil {
		return nil
	}
	return &AlterViewStatement{
		View:        cloneTableNamePtr(node.View),
		Columns:     cloneColumns(node.Columns),
		Select:      cloneSelect(node.Select),
		Algorithm:   node.Algorithm,
		Definer:     node.Definer,
		SQLSecurity: node.SQLSecurity,
		CheckOption: node.CheckOption,
	}
}

func cloneColName(node *ColName) *ColName {
	if node == nil {
		return nil
	}
	return &ColName{
		Metadata:  node.Metadata, // Shallow copy for metadata
		Name:      cloneIdentifierCI(node.Name),
		Qualifier: cloneTableName(node.Qualifier),
	}
}

func cloneLiteral(node *Literal) *Literal {
	if node == nil {
		return nil
	}
	return &Literal{
		Type: node.Type,
		Val:  node.Val,
	}
}

func cloneNullVal(node *NullVal) *NullVal {
	if node == nil {
		return nil
	}
	return &NullVal{}
}

func cloneAliasedTableExpr(node *AliasedTableExpr) *AliasedTableExpr {
	if node == nil {
		return nil
	}
	return &AliasedTableExpr{
		Expr:       cloneSimpleTableExpr(node.Expr),
		Partitions: clonePartitions(node.Partitions),
		As:         cloneIdentifierCS(node.As),
		Hints:      cloneIndexHints(node.Hints),
	}
}

func cloneAliasedExpr(node *AliasedExpr) *AliasedExpr {
	if node == nil {
		return nil
	}
	return &AliasedExpr{
		Expr: cloneExpr(node.Expr),
		As:   cloneIdentifierCI(node.As),
	}
}

func cloneStarExpr(node *StarExpr) *StarExpr {
	if node == nil {
		return nil
	}
	return &StarExpr{
		TableName: cloneTableName(node.TableName),
	}
}

func cloneWhere(node *Where) *Where {
	if node == nil {
		return nil
	}
	return &Where{
		Type: node.Type,
		Expr: cloneExpr(node.Expr),
	}
}

func cloneLimit(node *Limit) *Limit {
	if node == nil {
		return nil
	}
	return &Limit{
		Offset:   cloneExpr(node.Offset),
		Rowcount: cloneExpr(node.Rowcount),
	}
}

func cloneOrder(node *Order) *Order {
	if node == nil {
		return nil
	}
	return &Order{
		Expr:      cloneExpr(node.Expr),
		Direction: node.Direction,
	}
}

func cloneUpdateExpr(node *UpdateExpr) *UpdateExpr {
	if node == nil {
		return nil
	}
	return &UpdateExpr{
		Name: cloneColName(node.Name),
		Expr: cloneExpr(node.Expr),
	}
}

func cloneTableName(node TableName) TableName {
	return TableName{
		Name:      cloneIdentifierCS(node.Name),
		Qualifier: cloneIdentifierCS(node.Qualifier),
	}
}

func cloneIdentifierCI(node IdentifierCI) IdentifierCI {
	return NewIdentifierCI(node.String())
}

func cloneIdentifierCS(node IdentifierCS) IdentifierCS {
	return NewIdentifierCS(node.String())
}

// Helper functions for cloning slices and pointers

func cloneBoolPtr(b *bool) *bool {
	if b == nil {
		return nil
	}
	val := *b
	return &val
}

func cloneTableNamePtr(node *TableName) *TableName {
	if node == nil {
		return nil
	}
	cloned := cloneTableName(*node)
	return &cloned
}

func cloneExpr(node Expr) Expr {
	if node == nil {
		return nil
	}
	cloned := Clone(node)
	if expr, ok := cloned.(Expr); ok {
		return expr
	}
	return node
}

func cloneSimpleTableExpr(node SimpleTableExpr) SimpleTableExpr {
	if node == nil {
		return nil
	}
	cloned := Clone(node)
	if expr, ok := cloned.(SimpleTableExpr); ok {
		return expr
	}
	return node
}

func cloneInsertRows(node InsertRows) InsertRows {
	if node == nil {
		return nil
	}
	cloned := Clone(node)
	if rows, ok := cloned.(InsertRows); ok {
		return rows
	}
	return node
}

func cloneSelectExprs(nodes SelectExprs) SelectExprs {
	if nodes == nil {
		return nil
	}
	result := make(SelectExprs, len(nodes))
	for i, node := range nodes {
		if cloned := Clone(node); cloned != nil {
			if expr, ok := cloned.(SelectExpr); ok {
				result[i] = expr
			}
		}
	}
	return result
}

func cloneTableExprs(nodes TableExprs) TableExprs {
	if nodes == nil {
		return nil
	}
	result := make(TableExprs, len(nodes))
	for i, node := range nodes {
		if cloned := Clone(node); cloned != nil {
			if expr, ok := cloned.(TableExpr); ok {
				result[i] = expr
			}
		}
	}
	return result
}

func cloneTableNames(nodes TableNames) TableNames {
	if nodes == nil {
		return nil
	}
	result := make(TableNames, len(nodes))
	for i, node := range nodes {
		result[i] = cloneTableName(node)
	}
	return result
}

func cloneColumns(nodes Columns) Columns {
	if nodes == nil {
		return nil
	}
	result := make(Columns, len(nodes))
	for i, node := range nodes {
		result[i] = cloneIdentifierCI(node)
	}
	return result
}

func cloneGroupBy(nodes GroupBy) GroupBy {
	if nodes == nil {
		return nil
	}
	result := make(GroupBy, len(nodes))
	for i, node := range nodes {
		result[i] = cloneExpr(node)
	}
	return result
}

func cloneOrderBy(nodes OrderBy) OrderBy {
	if nodes == nil {
		return nil
	}
	result := make(OrderBy, len(nodes))
	for i, node := range nodes {
		result[i] = cloneOrder(node)
	}
	return result
}

func cloneUpdateExprs(nodes UpdateExprs) UpdateExprs {
	if nodes == nil {
		return nil
	}
	result := make(UpdateExprs, len(nodes))
	for i, node := range nodes {
		result[i] = cloneUpdateExpr(node)
	}
	return result
}

func clonePartitions(nodes Partitions) Partitions {
	if nodes == nil {
		return nil
	}
	result := make(Partitions, len(nodes))
	for i, node := range nodes {
		result[i] = cloneIdentifierCI(node)
	}
	return result
}

func cloneOnDup(nodes OnDup) OnDup {
	if nodes == nil {
		return nil
	}
	result := make(OnDup, len(nodes))
	for i, node := range nodes {
		result[i] = cloneUpdateExpr(node)
	}
	return result
}

func cloneIndexHints(node *IndexHints) *IndexHints {
	if node == nil {
		return nil
	}
	return &IndexHints{
		Type:    node.Type,
		Indexes: cloneColumns(node.Indexes),
	}
}

func cloneTableNamePtrs(nodes []*TableName) []*TableName {
	if nodes == nil {
		return nil
	}
	result := make([]*TableName, len(nodes))
	for i, node := range nodes {
		result[i] = cloneTableNamePtr(node)
	}
	return result
}

func cloneColNamePtrs(nodes []*ColName) []*ColName {
	if nodes == nil {
		return nil
	}
	result := make([]*ColName, len(nodes))
	for i, node := range nodes {
		result[i] = cloneColName(node)
	}
	return result
}

func cloneColumnDefinitions(nodes []*ColumnDefinition) []*ColumnDefinition {
	if nodes == nil {
		return nil
	}
	result := make([]*ColumnDefinition, len(nodes))
	for i, node := range nodes {
		result[i] = cloneColumnDefinition(node)
	}
	return result
}

func cloneColumnDefinition(node *ColumnDefinition) *ColumnDefinition {
	if node == nil {
		return nil
	}
	return &ColumnDefinition{
		Name:          cloneColName(node.Name),
		Type:          cloneColumnType(node.Type),
		NotNull:       node.NotNull,
		AutoIncrement: node.AutoIncrement,
		Default:       cloneExpr(node.Default),
		Comment:       node.Comment,
		PrimaryKey:    node.PrimaryKey,
		Unique:        node.Unique,
	}
}

func cloneColumnType(node *ColumnType) *ColumnType {
	if node == nil {
		return nil
	}
	return &ColumnType{
		Type:     node.Type,
		Length:   node.Length,
		Scale:    node.Scale,
		Unsigned: node.Unsigned,
		Zerofill: node.Zerofill,
	}
}

func cloneTableConstraints(nodes []*TableConstraint) []*TableConstraint {
	if nodes == nil {
		return nil
	}
	result := make([]*TableConstraint, len(nodes))
	for i, node := range nodes {
		result[i] = cloneTableConstraint(node)
	}
	return result
}

func cloneTableConstraint(node *TableConstraint) *TableConstraint {
	if node == nil {
		return nil
	}
	return &TableConstraint{
		Type:       node.Type,
		Name:       node.Name,
		Columns:    cloneColNamePtrs(node.Columns),
		References: cloneForeignKeyReference(node.References),
		CheckExpr:  cloneExpr(node.CheckExpr),
	}
}

func cloneForeignKeyReference(node *ForeignKeyReference) *ForeignKeyReference {
	if node == nil {
		return nil
	}
	return &ForeignKeyReference{
		Table:    cloneTableNamePtr(node.Table),
		Columns:  cloneColNamePtrs(node.Columns),
		OnDelete: node.OnDelete,
		OnUpdate: node.OnUpdate,
	}
}

func cloneTableOptions(nodes []*TableOption) []*TableOption {
	if nodes == nil {
		return nil
	}
	result := make([]*TableOption, len(nodes))
	for i, node := range nodes {
		result[i] = cloneTableOption(node)
	}
	return result
}

func cloneTableOption(node *TableOption) *TableOption {
	if node == nil {
		return nil
	}
	return &TableOption{
		Name:  node.Name,
		Value: node.Value,
	}
}

func cloneAlterActions(nodes []*AlterAction) []*AlterAction {
	if nodes == nil {
		return nil
	}
	result := make([]*AlterAction, len(nodes))
	for i, node := range nodes {
		result[i] = cloneAlterAction(node)
	}
	return result
}

func cloneAlterAction(node *AlterAction) *AlterAction {
	if node == nil {
		return nil
	}
	return &AlterAction{
		Type:       node.Type,
		Column:     cloneColumnDefinition(node.Column),
		OldName:    cloneColName(node.OldName),
		NewName:    cloneColName(node.NewName),
		Constraint: cloneTableConstraint(node.Constraint),
		After:      cloneColName(node.After),
		First:      node.First,
	}
}

// Clone functions for new node types

func cloneSubquery(node *Subquery) *Subquery {
	if node == nil {
		return nil
	}
	return &Subquery{
		Select: cloneSelect(node.Select),
	}
}

func cloneJoinTableExpr(node *JoinTableExpr) *JoinTableExpr {
	if node == nil {
		return nil
	}
	return &JoinTableExpr{
		LeftExpr:  cloneTableExpr(node.LeftExpr),
		Join:      node.Join,
		RightExpr: cloneTableExpr(node.RightExpr),
		Condition: cloneJoinCondition(node.Condition),
	}
}

func cloneOnCondition(node *OnCondition) *OnCondition {
	if node == nil {
		return nil
	}
	return &OnCondition{
		Expr: cloneExpr(node.Expr),
	}
}

func cloneUsingCondition(node *UsingCondition) *UsingCondition {
	if node == nil {
		return nil
	}
	return &UsingCondition{
		Cols: cloneColumns(node.Cols),
	}
}

func cloneFuncExpr(node *FuncExpr) *FuncExpr {
	if node == nil {
		return nil
	}
	return &FuncExpr{
		Qualifier: cloneTableName(node.Qualifier),
		Name:      cloneIdentifierCI(node.Name),
		Distinct:  node.Distinct,
		Exprs:     cloneSelectExprs(node.Exprs),
	}
}

func cloneTableExpr(node TableExpr) TableExpr {
	if node == nil {
		return nil
	}
	switch n := node.(type) {
	case *AliasedTableExpr:
		return cloneAliasedTableExpr(n)
	case *JoinTableExpr:
		return cloneJoinTableExpr(n)
	default:
		return node
	}
}

func cloneJoinCondition(node JoinCondition) JoinCondition {
	if node == nil {
		return nil
	}
	switch n := node.(type) {
	case *OnCondition:
		return cloneOnCondition(n)
	case *UsingCondition:
		return cloneUsingCondition(n)
	default:
		return node
	}
}